﻿// Inner Fire 游戏引擎库
// 普通Windows类
//
// Copyright (c) 2024 by 尔西大帝. All rights reserved.
//
// 合并用于Windows的工具
//
// Author: 尔西大帝
// Email: 2670613871@qq.com
// Created: 2024-11-29

#include "WindowApp.h"
#include "../Resource.h"
#include <codecvt>
#include <dx/view/dx_camera.h>
#include <game/core/timer.h>
#include <klog.h>
#include <windowsx.h>

#define MAX_LOADSTRING 100
constexpr const char* CONFIG_FILE = "config.xml";

using namespace dx;

// 全局变量:
static WindowApp* win_app_ = nullptr;
static ikit::XmlReader config(CONFIG_FILE);
static HINSTANCE hInst;                     // 当前实例
static WCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口类名
static ikit::Stopwatch loader_timer;        // 用于记录载入时间
static ikit::Stopwatch total_loader_timer;  // 用于记录载入时间

static LRESULT CALLBACK MainWndProc(
    HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
  return win_app_->WndProc(hWnd, message, wParam, lParam);
}

static INT_PTR CALLBACK MainAbout(
    HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
  return win_app_->About(hWnd, message, wParam, lParam);
}

// 打印并重新启动计时器
static void FlushTimer(const char* title) {
  loader_timer.Stop();
  KLOG("%s: %.2fs", title, loader_timer.GetElapsedMilliseconds() * 0.001f);
  loader_timer.Start();
}

WindowApp::WindowApp() : splash(), is_running_(false), is_loaded_(false) {}

WindowApp::~WindowApp() {
  // 需要先释放DX。因为DX中的资源需要FlushCommandList来清理
  delete dx_;
  delete gm_;
}

std::wstring string_to_wstring(const std::string& str) {
  std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
  return converter.from_bytes(str);
}

std::wstring string_to_wstring2(const std::string& str) {
  std::wstring wstr;
  wstr.resize(str.size());
  std::mbstowcs(&wstr[0], str.c_str(), str.size());
  return wstr;
}

bool WindowApp::Init(HINSTANCE hInstance, int nCmdShow) {
  // 开始计时
  total_loader_timer.Start();
  loader_timer.Start();

  win_app_ = this;
  hInstance_ = hInstance;

  // 这行用来适应4K屏幕
  SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);

  // 初始化全局字符串
  LoadStringW(hInstance, IDC_ALLOYMAIDEN, szWindowClass, MAX_LOADSTRING);
  MyRegisterClass(hInstance);

  // 配置一些全局的信息
  auto& cfg = config.Root();
  ikit::set_max_log_line(cfg("MaxLogLine", 2048));

  // 根据传入的工作区大小，计算整个窗口需要的大小
  client_width_ = cfg("Width", 1920);
  client_height_ = cfg("Height", 1080);
  RECT adjust_rect = {0, 0, static_cast<LONG>(client_width_),
      static_cast<LONG>(client_height_)};
  AdjustWindowRect(&adjust_rect, WS_OVERLAPPEDWINDOW, false);
  width_ = adjust_rect.right - adjust_rect.left;
  height_ = adjust_rect.bottom - adjust_rect.top;
  // 获取屏幕尺寸
  int screenWidth = GetSystemMetrics(SM_CXSCREEN);
  int screenHeight = GetSystemMetrics(SM_CYSCREEN);
  // 计算窗口的起始位置
  int x = (screenWidth - width_) / 2;
  int y = (screenHeight - height_) / 2;

  const char* app_name = cfg("AppName", "Inner Fire Engine");
  auto title = ikit::to_wide(app_name);
  hwnd_ = CreateWindowW(szWindowClass, title.c_str(), WS_OVERLAPPEDWINDOW, x, y,
      width_, height_, nullptr, nullptr, hInstance, nullptr);

  if (!hwnd_) {
    return FALSE;
  }
  // 启动计时器
  game::g_timer.Reset();

  ShowWindow(hwnd_, nCmdShow);
  UpdateWindow(hwnd_);
  accel_table_ = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_ALLOYMAIDEN));

  FlushTimer("Init Windows");

  return TRUE;
}

void WindowApp::Load() {
  const int STAGE_CREATE_DX = 0;
  const int STAGE_GM = 1;
  const int STAGE_INPUT = 2;
  const int STAGE_CONFIG = 3;
  const int STAGE_INIT_DIRECTX = 4;
  const int STAGE_INIT_PIPELINE = 5;
  const int STAGE_LOAD_SCENE = 6;
  const int STAGE_END = 100;

  if (current_load_stage_ == STAGE_CREATE_DX) {
    // 初始化DX
    dx_ = new dx::DxGraphics();

    current_load_stage_ = STAGE_GM;
    DrawSplash(0.1f);
  } else if (current_load_stage_ == STAGE_GM) {
    // 初始化GM
    gm_ = new game::GameManager("settings/game/game.xml");
    gm_->SetGraphics(dx_);

    current_load_stage_ = STAGE_INPUT;
    DrawSplash(0.2f);
    FlushTimer("Load GameManager");

  } else if (current_load_stage_ == STAGE_INPUT) {
    // 初始化Input
    gm_->GetInput()->Initialize(hwnd_);

    current_load_stage_ = STAGE_CONFIG;
    DrawSplash(0.3f);
  } else if (current_load_stage_ == STAGE_CONFIG) {
    // 设置DirectX的配置信息
    dx_->LoadConfig("settings/directx.xml");
    // 获得一些配置信息
    is_lock_frame = dx_->Config()("LockFrame");

    current_load_stage_ = STAGE_INIT_DIRECTX;
    DrawSplash(0.4f);
  } else if (current_load_stage_ == STAGE_INIT_DIRECTX) {
    // 初始化基础信息
    dx_->InitDirect3D(hwnd_, client_width_, client_height_);
    // 这里需要Resize一次，建立缓冲区
    dx_->OnResize();

    current_load_stage_ = STAGE_INIT_PIPELINE;
    DrawSplash(0.5f);
  } else if (current_load_stage_ == STAGE_INIT_PIPELINE) {
    // 初始化Pipeline（在执行此项目之后，is_inited设置TRUE）
    dx_->InitPipeline();
    FlushTimer("Init DirectX3D");

    current_load_stage_ = STAGE_LOAD_SCENE;
    DrawSplash(0.75f);
  } else if (current_load_stage_ == STAGE_LOAD_SCENE) {
    // 载入初始化场景
    gm_->CurrentScene()->Load();
    FlushTimer("Init Scene");

    current_load_stage_ = STAGE_END;
    DrawSplash(0.9f);
  } else if (current_load_stage_ == STAGE_END) {
    Sleep(50);
    // 结束计时
    total_loader_timer.Stop();
    KLOG("Total Load Time: %.2fs",
        total_loader_timer.GetElapsedMilliseconds() * 0.001f);

    DrawSplash(1.0);
    is_loaded_ = true;
    // 成功创建Windows之后，才能运行
    is_running_ = true;
  }
}

void WindowApp::Update() {
  game::g_timer.Tick();

  if (!app_paused_) {
    if (game::g_timer.CheckFixedUpdate()) {

      // 游戏的更新
      gm_->FixedUpdate();
      // 渲染的更新
      dx_->FixedUpdate();

      // 进行绘制
      if (is_lock_frame) {
        game::g_timer.CalculateFps();
        if (dx_->IsReady()) {
          dx_->Update();
          dx_->Draw();
        }
      }
    }

    if (is_lock_frame == false) {
      game::g_timer.CalculateFps();
    }
  }

  // 以秒为计时的更新（用于缓慢更新的项目）
  static float second_timeout = 0.0f;
  second_timeout += game::g_timer.deltaTime;
  if (second_timeout > 1.0f) {
    second_timeout = 0.0f;
    dx_->UpdateSecond();
  }

  if (is_lock_frame == false) {
    dx_->Update();
    dx_->Draw();
  }
}

void WindowApp::RollEvents() {
  if (PeekMessage(&msg_, nullptr, 0, 0, PM_REMOVE)) {
    if (!TranslateAccelerator(msg_.hwnd, accel_table_, &msg_)) {
      TranslateMessage(&msg_);
      DispatchMessage(&msg_);
    }
    // 如果是退出消息，则退出循环
    if (msg_.message == WM_QUIT) {
      is_running_ = false;
    }
  }
}

int WindowApp::OnResize(WPARAM wParam, LPARAM lParam) {
  if (dx_ == nullptr) {
    return 0;
  }
  // 重新设置窗口
  dx_->SetClientSize(LOWORD(lParam), HIWORD(lParam));

  // 如果没有初始化，不进行改变
  if (!dx_->IsReady()) {
    return 0;
  }

  // 根据情况进行更改
  if (wParam == SIZE_MINIMIZED) {
    app_paused_ = true;
    minimized_ = true;
    maximized_ = false;
  } else if (wParam == SIZE_MAXIMIZED) {
    app_paused_ = false;
    minimized_ = false;
    maximized_ = true;
    dx_->OnResize();
  } else if (wParam == SIZE_RESTORED) {
    // 从最小化恢复?
    if (minimized_) {
      app_paused_ = false;
      maximized_ = false;
      dx_->OnResize();
    }
    // 还是从最大化恢复?
    else if (maximized_) {
      app_paused_ = false;
      maximized_ = false;
      dx_->OnResize();
    } else if (is_resizing_) {
      // 如果用户正在拖动调整大小的滑块，我们在这里不调整缓冲区的大小，
      // 因为当用户持续拖动调整大小的滑块时，会向窗口发送一连串的 WM_SIZE 消息，
      // 对于每次从拖动调整大小滑块收到的
      // WM_SIZE消息都进行调整是没有意义的（而且会很慢）。
      // 因此，我们改为在用户完成调整窗口大小并释放调整大小滑块后，
      // 即发送 WM_EXITSIZEMOVE 消息时，再进行重置。
    } else { // API call such as SetWindowPos or mSwapChain->SetFullscreenState.
      dx_->OnResize();
    }
  }

  return 0;
}

void WindowApp::DrawSplash(float progress) {
  if (progress < 0.0f) progress = 0.0f;
  if (progress > 1.0f) progress = 1.0f;

  splash.SetProgress(progress);

  // 假设 hWnd 是你的窗口句柄
  InvalidateRect(hwnd_, &splash.progress_rect, FALSE); // 标记整个窗口为无效区域
  UpdateWindow(hwnd_);                                 // 强制立即重绘
}

WindowApp* WindowApp::GetApp() { return win_app_; }
ikit::XmlReader WindowApp::GetConfig() { return config; }
//
//  函数: MyRegisterClass()
//
//  目标: 注册窗口类。
//
ATOM WindowApp::MyRegisterClass(HINSTANCE hInstance) {
  WNDCLASSEXW wcex;

  wcex.cbSize = sizeof(WNDCLASSEX);

  wcex.style = CS_HREDRAW | CS_VREDRAW;
  wcex.lpfnWndProc = MainWndProc;
  wcex.cbClsExtra = 0;
  wcex.cbWndExtra = 0;
  wcex.hInstance = hInstance;
  wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ALLOYMAIDEN));
  wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
  wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
  // wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_ALLOYMAIDEN);
  wcex.lpszMenuName = NULL; // 不使用菜单
  wcex.lpszClassName = szWindowClass;
  wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

  return RegisterClassExW(&wcex);
}

int WindowApp::OnSizeMove(bool is_enter) {
  if (is_enter) {
    app_paused_ = true;
    is_resizing_ = true;
    game::g_timer.Stop();
  } else {
    app_paused_ = false;
    is_resizing_ = false;
    game::g_timer.Start();
    dx_->OnResize();
  }
  return 0;
}

void WindowApp::OnMouseDown(WPARAM btn_state, int button_id, int x, int y) {
  dx_->OnMouseDown(btn_state, x, y);

  bool hitted = gm_->GetUIManager()->OnMouseDown(
      XMFLOAT2(static_cast<float>(x), static_cast<float>(y)));
  // 只有当没有触发UI项目的时候，鼠标点击事件才发动
  if (!hitted) {
    gm_->GetLua()->OnMouseEvent(button_id, x, y, true);
  }
}

void WindowApp::OnMouseUp(WPARAM btn_state, int button_id, int x, int y) {
  dx_->OnMouseUp(btn_state, x, y);

  bool hitted = gm_->GetUIManager()->OnMouseUp(
      XMFLOAT2(static_cast<float>(x), static_cast<float>(y)));
  // 只有当没有触发UI项目的时候，鼠标点击事件才发动
  if (!hitted) {
    gm_->GetLua()->OnMouseEvent(button_id, x, y, false);
  }
}

void WindowApp::OnMouseMove(WPARAM btn_state, int x, int y) {
  if (!is_running_) return;

  // 这个需要先获得，否则在Dx里面改变了就不能计算move。
  auto last_pos = dx_->GetLastMousePos();
  float move_x = static_cast<float>(x - last_pos.x);
  float move_y = static_cast<float>(y - last_pos.y);

  // 处理Graphics里面的鼠标功能
  dx_->OnMouseMove(btn_state, x, y);

  last_pos = dx_->GetLastMousePos(); // 刷新最新的项目
  auto mouse_pos = XMFLOAT2((float)last_pos.x, (float)last_pos.y);
  gm_->GetUIManager()->OnMouseMove(XMFLOAT2(move_x, move_y), mouse_pos);
}

void WindowApp::OnKeyDown(WPARAM key_state) {
  gm_->GetLua()->OnKeyDown((int)key_state);
}

void WindowApp::OnKeyUp(WPARAM key_state) {
  gm_->GetLua()->OnKeyUp((int)key_state);
}

void WindowApp::OnChar(WPARAM char_state) {
  gm_->GetLua()->OnChar((int)char_state);
}

LRESULT WindowApp::OnCommand(
    HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
  int wmId = LOWORD(wParam);
  // 分析菜单选择:
  switch (wmId) {
  case IDM_ABOUT:
    DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, MainAbout);
    break;
  case IDM_EXIT:
    DestroyWindow(hWnd);
    break;
  default:
    return DefWindowProc(hWnd, message, wParam, lParam);
  }
  return 0;
}
LRESULT WindowApp::OnMouseWheel(WPARAM wParam, LPARAM lParam) {
  // 获取滚轮的滚动量
  // >0 = 向上滚动, <0 = 向下滚动
  int delta = GET_WHEEL_DELTA_WPARAM(wParam);
  dx_->OnMouseWheel(delta);

  return 0;
}

// ============================================================================
// 主窗口消息处理
// ============================================================================
LRESULT CALLBACK WindowApp::WndProc(
    HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
  switch (message) {
  case WM_CREATE:
    splash.LoadPicture(hWnd, L"textures\\InnerFire.png");
    return 0;
  case WM_INPUT:
    gm_->GetInput()->ProcessInputMessage(lParam);
    return 0;
  case WM_COMMAND:
    return OnCommand(hWnd, message, wParam, lParam);
  case WM_SIZE:
    return OnResize(wParam, lParam);
  // WM_EXITSIZEMOVE（当玩家开始拖动大小）
  case WM_ENTERSIZEMOVE:
    return OnSizeMove(true);
  // WM_EXITSIZEMOVE （当玩家拖动大小结束），这时候才进行一并的计算
  case WM_EXITSIZEMOVE:
    return OnSizeMove(false);
  case WM_MOUSEWHEEL:
    return OnMouseWheel(wParam, lParam);
  // 防止窗口过小.
  case WM_GETMINMAXINFO:
    ((MINMAXINFO*)lParam)->ptMinTrackSize.x = 320;
    ((MINMAXINFO*)lParam)->ptMinTrackSize.y = 240;
    return 0;
  case WM_LBUTTONDOWN:
    OnMouseDown(wParam, 0, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
    return 0;
  case WM_MBUTTONDOWN:
    OnMouseDown(wParam, 2, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
    return 0;
  case WM_RBUTTONDOWN:
    OnMouseDown(wParam, 1, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
    return 0;
  case WM_LBUTTONUP:
    OnMouseUp(wParam, 0, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
    return 0;
  case WM_MBUTTONUP:
    OnMouseUp(wParam, 2, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
    return 0;
  case WM_RBUTTONUP:
    OnMouseUp(wParam, 1, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
    return 0;

  case WM_KEYDOWN:
    OnKeyDown(wParam);
    return 0;
  case WM_KEYUP:
    OnKeyUp(wParam);
    return 0;
  case WM_CHAR:
    OnChar(wParam);
    return 0;
  case WM_MOUSEMOVE:
    OnMouseMove(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
    return 0;
  case WM_PAINT: {
    splash.DoPaint(hWnd); // 替换为你的图片路径
  } break;
  case WM_DESTROY:
    PostQuitMessage(0);
    break;
  default:
    return DefWindowProc(hWnd, message, wParam, lParam);
  }
  return 0;
}

// “关于”框的消息处理程序。
INT_PTR CALLBACK WindowApp::About(
    HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) {
  UNREFERENCED_PARAMETER(lParam);
  switch (message) {
  case WM_INITDIALOG:
    return (INT_PTR)TRUE;

  case WM_COMMAND:
    if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) {
      EndDialog(hDlg, LOWORD(wParam));
      return (INT_PTR)TRUE;
    }
    break;
  }
  return (INT_PTR)FALSE;
}
